
// ===============================================================================================================
// -*- C++ -*-
//
// Math.cpp - Some routines here are optimized with SIMD intrinsics and machine code. Aproximation is also used.
//
// Copyright (c) 2011 Guilherme R. Lampert
// guilherme.ronaldo.lampert@gmail.com
//
// This code is licenced under the MIT license.
//
// This software is provided "as is" without express or implied
// warranties. You may freely copy and compile this source into
// applications you distribute provided that the copyright text
// above is included in the resulting source code.
//
// ===============================================================================================================

#include <Math.hpp>

// C++ std includes
#include <cstdlib>
#include <cmath>
#include <ctime>

// Definitions of useful mathematical constants:

const float Math::PI = float(4.0 * atan(1.0));
const float Math::TWO_PI  = (float(2.0) * Math::PI);
const float Math::HALF_PI = (float(0.5) * Math::PI);
const float Math::INV_PI  = (float(1.0) / Math::PI);
const float Math::INV_TWO_PI = (float(1.0) / Math::TWO_PI);
const float Math::DEG_TO_RAD = (Math::PI / float(180.0));
const float Math::RAD_TO_DEG = (float(180.0) / Math::PI);

// Math definitions:

// Note: Here I use some machine code black magic in a couple of functions ;)
// Right now I do not provide any other alternative for other platforms/compilers, just the x86 asm code
// for visual c++. So if you can't/don't want to use it you will just have to replace it by yourself.

float Math::Sine(float x)
{
	float result;

	__asm fld dword ptr [x]
	__asm fsin
	__asm fstp dword ptr [result]

	return (result);
}

float Math::Cosine(float x)
{
	float result;

	__asm fld dword ptr [x]
	__asm fcos
	__asm fstp dword ptr [result]

	return (result);
}

void Math::SineCosine(float ang, float & s, float & c)
{
	__asm fld dword ptr [ang]
	__asm fsincos // fsincos gives both the sine and cosine of an angle in one shot!

	__asm mov eax, dword ptr [s]
	__asm mov ebx, dword ptr [c]

	__asm fstp dword ptr [ebx]
	__asm fstp dword ptr [eax]
}

float Math::ArcSine(float x)
{
	// Clamp input to [-1,1]

	if (x <= float(-1.0))
	{
		return (float(-Math::HALF_PI));
	}
	else if (x >= float(1.0))
	{
		return (float(Math::HALF_PI));
	}
	else
	{
		return (float(asin(double(x))));
	}
}

float Math::ArcCosine(float x)
{
	// Clamp value to [-1,1]

	if (x <= float(-1.0))
	{
		return (float(Math::PI));
	}
	else if (x >= float(1.0))
	{
		return (float(0.0));
	}
	else
	{
		return (float(acos(double(x))));
	}
}

float Math::Tangent(float x)
{
	return (float(tan(double(x))));
}

float Math::ArcTangent(float x)
{
	return (float(atan(double(x))));
}

float Math::ArcTangent(float x, float y)
{
	return (float(atan2(double(x), double(y))));
}

float Math::Hypotenuse(float x, float y)
{
	return (Math::Sqrt((x * x) + (y * y)));
}

float Math::Sqrt(float x)
{
	float result;

	// Fast SSE square root:
	__asm sqrtss xmm0, dword ptr [x]
	__asm movss dword ptr [result], xmm0

	return (result);
}

float Math::InvSqrt(float x)
{
	float result;

	// Fast SSE reciprocal square root:
	__asm rsqrtss xmm0, dword ptr [x]
	__asm movss dword ptr [result], xmm0

	return (result);
}

float Math::Ceil(float x)
{
	return (float(ceil(double(x))));
}

float Math::Floor(float x)
{
	return (float(floor(double(x))));
}

int Math::Truncate(float x)
{
	int result;

	__asm fld dword ptr [x]
	__asm fistp dword ptr [result]

	return (result);
}

bool Math::IsNAN(float x)
{
	// Cool FP trick by John Carmack!
	// NANs never compare equal.
	return (x != x);
}

void Math::SeedRandomGenerator(void)
{
	time_t now = time(0);
	unsigned char * p = reinterpret_cast<unsigned char *>(&now);
	unsigned int seed = 0;

	for (size_t i = 0; i < sizeof(now); ++i)
	{
		seed = seed * (UCHAR_MAX + 2U) + p[i];
	}

	srand(seed);
}

float Math::UniformRandom(void)
{
	return (static_cast<float>(rand() * (1.0f / (RAND_MAX + 1.0f))));
}

int Math::RandomNumber(int min, int max)
{
	return (static_cast<int>(min + Math::UniformRandom() * (max - min)));
}